package de.uni.muenster.idw2;

import java.util.ArrayList;
import java.util.LinkedList;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

import com.indoornavi.verbal.graph.Graph;
import com.indoornavi.verbal.graph.Vertex;

import de.uni.muenster.idw2.data.Building;
import de.uni.muenster.idw2.data.Connection;
import de.uni.muenster.idw2.data.Floor;
import de.uni.muenster.idw2.data.ImagePosition;
import de.uni.muenster.idw2.data.MyImage;
import de.uni.muenster.idw2.data.Node;
import de.uni.muenster.idw2.data.Position;
import de.uni.muenster.idw2.data.Room;
import de.uni.muenster.idw2.dialogs.ImageDialog;
import de.uni.muenster.idw2.utilities.Utils;
import de.uni.muenster.idw2.view.MyPhotoView;

public class MapView extends View {
	Building building;

	private Drawable mIcon;
	private float mPosX;
	private float mPosY;

	private float mLastTouchX;
	private float mLastTouchY; 

	private float mFirstTouchX;
	private float mFirstTouchY;

	private int move;

	private boolean click;

	private static final int INVALID_POINTER_ID = -1;
	// The active pointer is the one currently moving our object.
	private int mActivePointerId = INVALID_POINTER_ID;

	private ScaleGestureDetector mScaleDetector;
	private float mScaleFactor = 1.f;

	// bounds of building
	double[] bounds;

	// conversion
	CoordinateConversion coordConv = new CoordinateConversion();
	int coordShiftNorthing;
	int coordShiftEasting;
	int dNorthing;
	int dEasting;
	
	// path
	com.indoornavi.verbal.graph.Path path;
	boolean pathInitialized = false;
	
	// floor switching
	int currFloor = 0; 
	int floorCount = 0;
	
	// User position
	boolean bDrawUserPosition = false;
	Position userPosition; 
	
	
	/**
	 * Constructor for the MapView. Calculates some basic values like utm
	 * coordinate shifts, extend of the building etc.
	 * 
	 * @param context
	 * @param building
	 */
	public MapView(Context context, Building building) {
		this(context, null, 0);
		this.building = building;
		floorCount = building.getFloors().size();
		
		// determine the bounds, min and max values and set shifts and extends
		// for x and y accordingly
		bounds = getBounds();
		double[] minUTM = coordConv.latLon2UTMDouble(bounds[0], bounds[1]);
		double[] maxUTM = coordConv.latLon2UTMDouble(bounds[2], bounds[3]);
		coordShiftNorthing = (int) Math.floor(minUTM[0]);
		coordShiftEasting = (int) Math.floor(minUTM[1]);
		dNorthing = (int) (maxUTM[0] - minUTM[0]);
		dEasting = (int) (maxUTM[1] - minUTM[1]);
		
		// experimental: integrate the route
		/*Graph g = new Graph("http://checkout.yourweb.de/graph.dat"); 
		com.indoornavi.verbal.graph.Path pth = g.Dijkstra("R0.01", "R0.06");
		this.path = pth;
		pathInitialized = true; */
		
		
		// Log.d("DEBUG", "***** coord shift: north " + coordShiftNorthing +
		// " | east " + coordShiftEasting);
	}

	public MapView(Context context, Building building, com.indoornavi.verbal.graph.Path path) {
		this(context, null, 0);
		this.building = building;
		floorCount = building.getFloors().size();
		
		// determine the bounds, min and max values and set shifts and extends
		// for x and y accordingly
		bounds = getBounds();
		double[] minUTM = coordConv.latLon2UTMDouble(bounds[0], bounds[1]);
		double[] maxUTM = coordConv.latLon2UTMDouble(bounds[2], bounds[3]);
		coordShiftNorthing = (int) Math.floor(minUTM[0]);
		coordShiftEasting = (int) Math.floor(minUTM[1]);
		dNorthing = (int) (maxUTM[0] - minUTM[0]);
		dEasting = (int) (maxUTM[1] - minUTM[1]);
		
		this.path = path;
		pathInitialized = true;
	}
	
	public MapView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public MapView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mIcon = context.getResources().getDrawable(R.drawable.icon);
		mIcon.setBounds(0, 0, mIcon.getIntrinsicWidth(),
				mIcon.getIntrinsicHeight());

		// Create our ScaleGestureDetector
		mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
	}

	private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
		
		@Override
		public void onReceive(Context context, Intent intent) {
			if(intent.getAction().equals(Utils.RECEIVER_MSG)){
				Position tmpPosition = (Position) intent.getExtras().getParcelable(Utils.LOCATION_UPDATE);
				userPosition = updateUserPosition(tmpPosition); 
				bDrawUserPosition = true;
			}
		}
	};
	
	/**
	 * Returns the canvas x coordinate how it was before the canvas was scaled
	 * and/or translated. e.g. for the same corner of the building, the
	 * coordinate should always be the same... hopefully
	 * 
	 * @param x
	 * @return
	 */
	public int getCanvasX(float x) {
		return Math.round((x - mPosX) / mScaleFactor);
	}

	/**
	 * same as getCanvasX but for y...
	 * 
	 * @param y
	 * @return
	 */
	public int getCanvasY(float y) {
		return Math.round((y - mPosY) / mScaleFactor);
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// Let the ScaleGestureDetector inspect all events.
		mScaleDetector.onTouchEvent(ev);

		final int action = ev.getAction();
		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN: {
			final float x = ev.getX();
			final float y = ev.getY();

			mFirstTouchX = x;
			mFirstTouchY = y;

			click = true;
			move = 0;

			Log.d("DEBUG", "Raw X/Y: (" + ev.getRawX() + "/" + ev.getRawY()
					+ ") " + "X/Y: (" + ev.getX() + "/" + ev.getY() + ")");

			Log.d("DEBUG", "canvas X/Y: " + getCanvasX(ev.getX()) + "/"
					+ getCanvasY(ev.getY()));

			mLastTouchX = x;
			mLastTouchY = y;
			mActivePointerId = ev.getPointerId(0);
			break;
		}

		case MotionEvent.ACTION_MOVE: {
			final int pointerIndex = ev.findPointerIndex(mActivePointerId);
			final float x = ev.getX(pointerIndex);
			final float y = ev.getY(pointerIndex);

			// Only move if the ScaleGestureDetector isn't processing a gesture.
			if (!mScaleDetector.isInProgress()) {
				final float dx = x - mLastTouchX;
				final float dy = y - mLastTouchY;

				final float distx = x - mFirstTouchX;
				final float disty = y - mFirstTouchY;

				move++;

				mPosX += dx;
				mPosY += dy;

				if ((Math.abs(distx) < 5) && (Math.abs(disty) < 5) && move < 5)
					click = true;
				else
					click = false;

				invalidate();
			}

			mLastTouchX = x;
			mLastTouchY = y;

			break;
		}

		case MotionEvent.ACTION_UP: {
			if (click) {
				// check which image has to be loaded
				// get position -> for each BUTTON is clicked?
				
				//userPosition = updateUserPosition(new Position(ev.getX(0), ev.getY(0), currFloor));    
				//bDrawUserPosition = true;
								
				/*ImagePosition[] points = new ImagePosition[3];
				points[0] = new ImagePosition(0.5f, 1.0f);
				points[1] = new ImagePosition(0.5f, 0.860339506f);
				points[2] = new ImagePosition(0.139979339f, 0.860339506f);

				MyImage photo = new MyImage(getContext(), points,
						"http://ifgibox.de/g_carr02/imgs_442_295/DSC_0004.JPG");
				MyPhotoView mpv = new MyPhotoView(getContext(), photo);
				ImageDialog id = new ImageDialog(getContext(), mpv);
				id.show();*/				
			}
			move = 0;
			mActivePointerId = INVALID_POINTER_ID;
			break;
		}

		case MotionEvent.ACTION_CANCEL: {
			mActivePointerId = INVALID_POINTER_ID;
			break;
		}

		case MotionEvent.ACTION_POINTER_UP: {
			final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
			final int pointerId = ev.getPointerId(pointerIndex);
			if (pointerId == mActivePointerId) {
				// This was our active pointer going up. Choose a new
				// active pointer and adjust accordingly.
				final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
				mLastTouchX = ev.getX(newPointerIndex);
				mLastTouchY = ev.getY(newPointerIndex);
				mActivePointerId = ev.getPointerId(newPointerIndex);
			}
			break;
		}
		}

		return true;
	}

	@Override
	public void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		// call of drawMap() method, just to keep the onDraw() method as clean
		// as possible
		drawMap(canvas);

		// Log.d("DEBUG", "building floors: " + building.getFloors().size());

		canvas.save();

		canvas.translate(mPosX, mPosY);
		canvas.scale(mScaleFactor, mScaleFactor);
		// mIcon.draw(canvas);

		canvas.restore();
	}

	/**
	 * Main method to draw the map. Calls drawRoom for each room.
	 * 
	 * @TODO: currently only draws the first floor
	 * @param canvas
	 */
	private void drawMap(Canvas canvas) {

		canvas.drawColor(Color.WHITE);
		// iterate over rooms of the building, for each: drawRoom()
		Log.d("DEBUG", "Number of floors in building object: "
				+ building.getFloors().size());
		for (Room room : building.getFloors().get(currFloor).getRooms()) {
			drawRoom(room, canvas);
		}

		// Now, draw room's text
		for (Room room : building.getFloors().get(currFloor).getRooms()) {
			if (!room.getName().contains("hallway")
					&& !room.getName().contains("foyer")) {
				double area = getRoomArea(room);
				int charCount = room.getName().length();
				if (area / charCount > 100) { // Establish a threshold to hide
												// text of rooms with low area
					double[] textOrigin = getRoomTextOrigin(room);
					drawRoomText(room.getName(), textOrigin, canvas);
				}
			}
		}
		
		// Draw route
		if (pathInitialized){
			ArrayList<Vertex> vertexList = path.getVertices();
			drawRoute(vertexList, canvas);
		}
			
		if (bDrawUserPosition){
	        Bitmap user = BitmapFactory.decodeResource(getResources(), R.drawable.user);
	    	canvas.drawBitmap(user, (float) scaleCoordX(userPosition.getX())-12, (float) scaleCoordY(userPosition.getY())-16, null);
		}
	}

	/**
	 * Calculate the area of the rooms based on the method found in
	 * http://www.mathopenref.com/coordpolygonarea.html
	 * 
	 * @param room
	 * @return area
	 */
	private double getRoomArea(Room room) {
		LinkedList<Node> nodelist = room.getNodes();
		ArrayList<Integer> xCoords = new ArrayList<Integer>();
		ArrayList<Integer> yCoords = new ArrayList<Integer>();

		int firstX = 0;
		int firstY = 0;
		int count = 0;

		for (Node node : nodelist) {
			count += 1;
			Position pos = node.getPosition();
			double x = pos.getX();
			double y = pos.getY();
			double[] currCanvasCoords = wgsToCanvas(new double[] { x, y });
			int xCanvas = scaleCoordX(Math.round(currCanvasCoords[0]));
			int yCanvas = scaleCoordY(Math.round(currCanvasCoords[1]));

			if (count == 1) {
				firstX = xCanvas;
				firstY = yCanvas;
			}

			// Store each pair of canvas coordinates
			xCoords.add(xCanvas);
			yCoords.add(yCanvas);
		}

		// Add again the first node's coordinates
		xCoords.add(firstX);
		yCoords.add(firstY);

		// Apply the method
		int diag1 = 0;
		int diag2 = 0;
		for (int i = 0; i < xCoords.size() - 1; i++) {
			diag1 += ((Integer) xCoords.get(i))
					* ((Integer) yCoords.get(i + 1));
			diag2 += ((Integer) xCoords.get(i + 1))
					* ((Integer) yCoords.get(i));
		}
		double area = (diag1 - diag2) / 2;
		//Log.d("AREA", "area: " + java.lang.Math.abs(area));
		return java.lang.Math.abs(area);
	}

	/**
	 * Iterates over the nodes of a room and extract the medium point (it is
	 * just an easy way to do it, but it doesn't guarantee the point is inside
	 * the room)
	 * 
	 * @return double[] array with the following order: mediumY, mediumX
	 */
	private double[] getRoomTextOrigin(Room room) {
		double minX, minY, maxX, maxY;
		minX = maxX = room.getNodes().getFirst().getPosition().getX();
		minY = maxY = room.getNodes().getFirst().getPosition().getY();

		for (Node n : room.getNodes()) {
			if (n.getPosition().getX() < minX) {
				minX = n.getPosition().getX();
			} else if (n.getPosition().getX() > maxX) {
				maxX = n.getPosition().getX();
			}
			if (n.getPosition().getY() < minY) {
				minY = n.getPosition().getY();
			} else if (n.getPosition().getY() > maxY) {
				maxY = n.getPosition().getY();
			}
		}
		double originX = (maxX + minX) / 2;
		double originY = (maxY + minY) / 2;
		double[] origin = { originX, originY };
		//Log.d("DEBUG", "*******ORIGIN: min " + origin[0] + "/" + origin[1]);
		return origin;

	}

	/**
	 * Iterates over the whole building object (all rooms actually) and collects
	 * minimum and maximum values for latitude and longitude.
	 * 
	 * @return double[] array containing in the following order: minX, minY,
	 *         maxX, maxY
	 */
	private double[] getBounds() {
		double minX, minY, maxX, maxY;
		minX = maxX = building.getFloors().getFirst().getRooms().getFirst()
				.getNodes().getFirst().getPosition().getX();
		minY = maxY = building.getFloors().getFirst().getRooms().getFirst()
				.getNodes().getFirst().getPosition().getY();
		for (Floor f : building.getFloors()) {
			for (Room r : f.getRooms()) {
				for (Node n : r.getNodes()) {
					if (n.getPosition().getX() < minX) {
						minX = n.getPosition().getX();
					} else if (n.getPosition().getX() > maxX) {
						maxX = n.getPosition().getX();
					}
					if (n.getPosition().getY() < minY) {
						minY = n.getPosition().getY();
					} else if (n.getPosition().getY() > maxY) {
						maxY = n.getPosition().getY();
					}
				}
			}
		}
		double[] bounds = { minX, minY, maxX, maxY };
		Log.d("DEBUG", "*******BOUNDS: min " + bounds[0] + "/" + bounds[1]
				+ " , max " + bounds[2] + "/" + bounds[3]);
		return bounds;
	}

	public void drawRoute(ArrayList<Vertex> vertexList, Canvas canvas) {

		Path routePath = new Path();
		double firstXwgs = vertexList.get(0).getCoord().getX();
		double firstYwgs = vertexList.get(0).getCoord().getY();
		double[] firstCanvasCoords = wgsToCanvas(new double[] { firstXwgs,
				firstYwgs });
		routePath.moveTo(scaleCoordX(firstCanvasCoords[0]),
				scaleCoordY(firstCanvasCoords[1]));
		
		for (Vertex v : vertexList) {
			double x = v.getCoord().getX();
			double y = v.getCoord().getY();
			
			double[] currCanvasCoords = wgsToCanvas(new double[] { x, y });
			
			Log.d("DEBUG", "ROUTE: " + x + " / " + y);
			Log.d("DEBUG", "ROUTE px coords: " + scaleCoordX(currCanvasCoords[0]) + " / " + 
					scaleCoordY(currCanvasCoords[1]));
			
			routePath.lineTo(scaleCoordX(currCanvasCoords[0]),
					scaleCoordY(currCanvasCoords[1]));
						
		}
		
		Paint routePaint = new Paint();
		routePaint.setColor(Color.RED);
		routePaint.setStyle(Style.STROKE);
		routePaint.setStrokeWidth(5);
		routePaint.setAntiAlias(true);
		canvas.drawPath(routePath, routePaint);
		
		// Draw the flag to indicate the goal
		double lastXwgs = vertexList.get(vertexList.size()-1).getCoord().getX();
		double lastYwgs = vertexList.get(vertexList.size()-1).getCoord().getY();
		double[] lastCanvasCoords = wgsToCanvas(new double[] { lastXwgs, lastYwgs });				
        Bitmap flag = BitmapFactory.decodeResource(getResources(), R.drawable.flag_finish);
        canvas.drawBitmap(flag, scaleCoordX(lastCanvasCoords[0])-4, scaleCoordY(lastCanvasCoords[1])-22, null);
	}

	/**
	 * Draws one room onto the canvas. Uses various scale and shift methods to
	 * calculate drawing coords.
	 * 
	 * @param room
	 * @param canvas
	 */
	private void drawRoom(Room room, Canvas canvas) {
		// Log.d("DEBUG", "room to draw: " + room.getName());

		LinkedList<Node> nodelist = room.getNodes();

		Path roompath = new Path();

		double firstXwgs = nodelist.getFirst().getPosition().getX();
		double firstYwgs = nodelist.getFirst().getPosition().getY();

		double[] firstCanvasCoords = wgsToCanvas(new double[] { firstXwgs,
				firstYwgs });
		roompath.moveTo(scaleCoordX(firstCanvasCoords[0]),
				scaleCoordY(firstCanvasCoords[1]));

		// iterate over each node of the room and draw the line
		for (Node node : nodelist) {
			Position pos = node.getPosition();
			double x = pos.getX();
			double y = pos.getY();
			double[] currCanvasCoords = wgsToCanvas(new double[] { x, y });

			roompath.lineTo(scaleCoordX(Math.round(currCanvasCoords[0])),
					scaleCoordY(Math.round(currCanvasCoords[1])));
		}
		roompath.lineTo(scaleCoordX(firstCanvasCoords[0]),
				scaleCoordY(firstCanvasCoords[1]));

		Paint roomPaint = new Paint();
		roomPaint.setColor(Color.rgb(200, 200, 200));
		roomPaint.setStyle(Style.FILL);
		roomPaint.setAntiAlias(true);

		Paint hallwayPaint = new Paint();
		hallwayPaint.setColor(Color.rgb(240, 240, 240));
		hallwayPaint.setStyle(Style.FILL);
		hallwayPaint.setAntiAlias(true);

		if (room.getName().contains("hallway")
				|| room.getName().contains("foyer")) {
			canvas.drawPath(roompath, hallwayPaint);
		} else {
			canvas.drawPath(roompath, roomPaint);
		}

		// draw the border of the rooms
		Paint borderPaint = new Paint();
		borderPaint.setColor(Color.GRAY);
		borderPaint.setStyle(Style.STROKE);
		borderPaint.setStrokeWidth(2);
		borderPaint.setAntiAlias(true);
		canvas.drawPath(roompath, borderPaint);

		// Clip a circle, draw the room (with doorPaint) -> doors are shown
		Paint doorPaint = new Paint();
		doorPaint.setColor(Color.DKGRAY);
		doorPaint.setStyle(Style.STROKE);
		doorPaint.setStrokeWidth(3*mScaleFactor);

		Path path = new Path();
		for (Connection conn : room.getConnections()) {
			Position pos = conn.getPosition1();
			double x = pos.getX();
			double y = pos.getY();
			double[] canvasCoords = wgsToCanvas(new double[] { x, y });
			canvas.save();
			float rectRatio = 3 * mScaleFactor;
			path.addCircle(scaleCoordX(Math.round(canvasCoords[0])), scaleCoordY(Math.round(canvasCoords[1])), rectRatio, Path.Direction.CW);
        	canvas.clipPath(path);  
			canvas.drawPath(roompath, doorPaint);
			canvas.restore();
		}

	}

	/**
	 * Draws the room's associated text onto the canvas. Uses various scale and
	 * shift methods to calculate drawing coords.
	 * 
	 * @param roomName
	 * @param textOrigin
	 * @param canvas
	 */
	private void drawRoomText(String roomName, double[] textOrigin,
			Canvas canvas) {
		Paint paint = new Paint();
		paint.setColor(Color.BLUE);
		paint.setTextAlign(Align.CENTER);
		paint.setShadowLayer(2f, 1, 1, Color.WHITE);
		paint.setTextSize(16);
		paint.setAntiAlias(true);

		double[] currCanvasCoords = wgsToCanvas(textOrigin);
		Log.d("DEBUG",
				"roomText: "
						+ Integer.toString(scaleCoordX(Math
								.round(currCanvasCoords[0])))
						+ " - "
						+ Integer.toString(scaleCoordY(Math
								.round(currCanvasCoords[1]))));
		canvas.drawText(roomName, scaleCoordX(Math.round(currCanvasCoords[0])),
				scaleCoordY(Math.round(currCanvasCoords[1])), paint);
	}
	
	/**
	 * Switch floor to the one below the current one
	 * @return true if possible, false if not
	 */
	public boolean prevLevel(){
		if (isPrevLevelAvailable()){
			currFloor--;
			invalidate();
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Switch floor to the one below the current one
	 * @return true if possible, false if not
	 */
	public boolean nextLevel(){
		if (isNextLevelAvailable()){
			currFloor++;
			invalidate();
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Find out whether a floor above the current one is available
	 * @return true if there is at least one more available
	 */
	public boolean isNextLevelAvailable(){
		return (currFloor < floorCount);
	}
	
	/**
	 * Find out whether a floor below the current one is available
	 * @return true if there is at least one more available
	 */
	public boolean isPrevLevelAvailable(){
		return (currFloor > 0);
	}
	
	
	/**
	 * Improve the coordinates of the user position by clamping them to a route
	 * @param position Raw user position 
	 * @return Position Improved position with improved coordinates
	 */
	private Position updateUserPosition(Position position){
		if (pathInitialized){ // Clamp the point to a route
			ArrayList<Vertex> vertexList = path.getVertices();
			double[] tmpPoint;
			double[] bestPoint = {Double.MAX_VALUE, Double.MAX_VALUE};;		
			double minDistance = Double.MAX_VALUE;
			double currentDistance;
			
			for (int i=0; i < vertexList.size() - 1; i++) {
				double[] currCanvasCoords = wgsToCanvas(new double[] { vertexList.get(i).getCoord().getX(), vertexList.get(i).getCoord().getY() });
				double x1 = currCanvasCoords[0];
				double y1 = currCanvasCoords[1];
				
				currCanvasCoords = wgsToCanvas(new double[] { vertexList.get(i+1).getCoord().getX(), vertexList.get(i+1).getCoord().getY() });
				double x2 = currCanvasCoords[0];
				double y2 = currCanvasCoords[1];
				
				currCanvasCoords = wgsToCanvas(new double[] { position.getX(), position.getY() });
				double x = currCanvasCoords[0];
				double y = currCanvasCoords[1];
				
				//double x = getCanvasX((float)position.getX());
				//double y = getCanvasY((float)position.getY());
				
				tmpPoint = nearestPointOnLine(x1, y1, x2, y2, x, y, true);
				currentDistance = java.lang.Math.hypot(tmpPoint[0] - x, tmpPoint[1] - y);
				//Log.d("COORD", "Distance: " + i + " " + currentDistance );
				if (currentDistance < minDistance) {
			    	minDistance = currentDistance;
			    	bestPoint = tmpPoint;
			    }  
			}
			position.setX(bestPoint[0]);
	    	position.setY(bestPoint[1]);
		}
		invalidate();
		return position;
	}
	

	/**
	 * Find the nearest point on a line to a given point
	 * 	   Adapted from http://pastebin.com/n9rUuGRh
	 * @param ax Coordinate X of the line's starting point 
	 * @param ay Coordinate Y of the line's starting point
	 * @param bx Coordinate X of the line's ending point
	 * @param by Coordinate Y of the line's ending point
	 * @param px Coordinate X of the given point
	 * @param py Coordinate Y of the given point
	 * @param clampToSegment Indicate if the point should be clamped to the segment
	 * @return double[] array with coordinates X and Y of the nearest point 
	 */
	public static double[] nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py,
			boolean clampToSegment) {
		// Thanks StackOverflow!
		// http://stackoverflow.com/questions/1459368/snap-point-to-a-line-java
		double apx = px - ax;
		double apy = py - ay;
		double abx = bx - ax;
		double aby = by - ay;

		double ab2 = abx * abx + aby * aby;
		double ap_ab = apx * abx + apy * aby;
		double t = ap_ab / ab2;
		if (clampToSegment) {
			if (t < 0) {
				t = 0;
			} else if (t > 1) {
				t = 1;
			}
		}
		double[] newPoint = {ax + abx * t, ay + aby * t};
		//Log.d("COORDS", newPoint[0] + " - " + newPoint[1]);
		return newPoint;
	}
		
	private double[] wgsToUtm(double[] coords) {
		return coordConv.latLon2UTMDouble(coords[0], coords[1]);
	}

	/**
	 * Shifts the coordinates by coordShiftNorthing and coordShiftEasting
	 * respectively
	 * 
	 * @param coords
	 * @return
	 */
	private double[] shiftCoords(double[] coords) {
		double[] shiftedCoords = { coords[0] - coordShiftNorthing,
				coords[1] - coordShiftEasting };
		return shiftedCoords;

	}

	/**
	 * The only of these methods you should call directly! Takes WGS84
	 * coordinates and returns canvas coordinates BEWARE: the swapped
	 * coordinates issue has been fixed, so call wgsToCanvas(x, y) as you would
	 * expect...
	 * 
	 * @param coords
	 * @return
	 */
	private double[] wgsToCanvas(double[] coords) {
		coords = wgsToUtm(coords);
		coords = shiftCoords(coords);
		coords[1] = dEasting - coords[1];
		coords = initialScaleCoords(coords);
		return coords;
	}

	/**
	 * When shifted the coordinates are 50 times 100 meters in range. Thus, they
	 * are scaled so that the building fills the whole screen
	 * 
	 * @param coords
	 * @return
	 */
	public double[] initialScaleCoords(double[] coords) {
		float initialScale = 480 / dNorthing;
		//Log.d("DEBUGG", "initialScale: " + initialScale + ", dEasting: "
		//		+ dEasting + ", dNorthing: " + dNorthing);
		coords[0] *= initialScale;
		coords[1] *= initialScale;
		return coords;
	}

	/**
	 * Helper function for the pinch zoom / translate functions. Returns the
	 * translated and scaled x canvas coord.
	 * 
	 * @param x
	 * @return
	 */
	public int scaleCoordX(double x) {
		return (int) Math.round(x * mScaleFactor + mPosX);
	}

	/**
	 * Like scaleCoordX() but for y...
	 * 
	 * @param y
	 * @return
	 */
	public int scaleCoordY(double y) {
		return (int) Math.round(y * mScaleFactor + mPosY);
	}

	private class ScaleListener extends
			ScaleGestureDetector.SimpleOnScaleGestureListener {
		@Override
		public boolean onScale(ScaleGestureDetector detector) {
			mScaleFactor *= detector.getScaleFactor();

			// Don't let the object get too small or too large.
			mScaleFactor = Math.max(0.3f, Math.min(mScaleFactor, 8.0f));
			
			click = false;
			
			invalidate();
			return true;
		}		
	}
	
	public void enableReceiver(Activity act){
		IntentFilter inetnetFilter =  new IntentFilter(Utils.RECEIVER_MSG);
		act.registerReceiver(broadcastReceiver, inetnetFilter);
	}
	
	public void disableReceiver(Activity act){
		act.unregisterReceiver(broadcastReceiver);
	}
}